package com.atmosphere;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.atmosphere.interfaces.IAtmosphere;
import com.atmosphere.interfaces.IComponent;
import com.atmosphere.interfaces.IComponentKey;
import com.jme.bounding.BoundingBox;
import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.state.CullState;
import com.jme.scene.state.FogState;
import com.jme.scene.state.ZBufferState;
import com.jme.scene.state.CullState.Face;
import com.jme.scene.state.FogState.Quality;
import com.jme.system.DisplaySystem;

/**
 * <code>Atmosphere</code> defines the implementation of the
 * central manager unit of the atmosphere system. Various
 * atmosphere components are required to be attached to this
 * manager unit to be properly updated and rendered.
 *
 * @author Yi Wang (Neakor)
 * @version Creation date: 05-19-2009 22:02 EST
 * @version Modified date: 05-21-2009 11:32 EST
 */
public class Atmosphere extends Node implements IAtmosphere {
	/**
	 * Serial version.
	 */
	private static final long serialVersionUID = 4464111447906784129L;
	/**
	 * The <code>Map</code> of <code>String</code> to <code>IComponent</code>.
	 */
	private final Map<String, IComponent> components;
	/**
	 * The <code>Camera</code> instance.
	 */
	private final Camera camera;
	/**
	 * The <code>Float</code> 24-hour, 100-minute ending time of day-time.
	 */
	private volatile float dayEnd;
	/**
	 * The <code>Float</code> 24-hour, 100-minute starting time of day-time.
	 */
	private volatile float dayStart;
	/**
	 * The <code>Float</code> 24-hour, 100-minute ending time of night-time.
	 */
	private volatile float nightEnd;
	/**
	 * The <code>Float</code> 24-hour, 100-minute starting time of night-time.
	 */
	private volatile float nightStart;
	/**
	 * The <code>Float</code> 24-hour, 100-minute time of the day-night transition-time.
	 */
	private volatile float dayTransition;
	/**
	 * The <code>Float</code> 24-hour, 100-minute time of the night-day transition-time.
	 */
	private volatile float nightTransition;
	/**
	 * The <code>Float</code> day-transition and day-end time difference.
	 */
	private volatile float diffTransDay;
	/**
	 * The <code>Float</code> night-start and day-transition time difference.
	 */
	private volatile float diffNightTrans;
	/**
	 * The <code>Float</code> night-transition and night-end time difference.
	 */
	private volatile float diffTransNight;
	/**
	 * The <code>Float</code> day-start and night-transition time difference.
	 */
	private volatile float diffDayTrans;
	
	/**
	 * Constructor of <code>Atmosphere</code>.
	 */
	public Atmosphere() {
		this.components = new ConcurrentHashMap<String, IComponent>();
		this.camera = DisplaySystem.getDisplaySystem().getRenderer().getCamera();
		this.setModes();
		this.setModelBound(new BoundingBox());
		this.updateModelBound();
		// Default time.
		this.setDayEnd(15, 0);
		this.setDayStart(9, 0);
		this.setNightEnd(3, 0);
		this.setNightStart(21, 0);
		this.setDayTransition(18, 0);
		this.setNightTransition(6, 0);
	}
	
	/**
	 * Set the texture combine mode, render queue mode, and cull mode.
	 */
	private void setModes() {
		this.setTextureCombineMode(TextureCombineMode.Replace);
		this.setRenderQueueMode(Renderer.QUEUE_SKIP);
		this.setCullHint(CullHint.Never);
		final Renderer renderer = DisplaySystem.getDisplaySystem().getRenderer();
		// Create non-writable z-buffer state.
		final ZBufferState zbuff = renderer.createZBufferState();
	    zbuff.setWritable(false);
	    zbuff.setEnabled(true);
	    zbuff.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
	    this.setRenderState(zbuff);
	    // Create non-visible fog state.
	    final FogState fog = renderer.createFogState();
	    fog.setEnabled(true);
	    fog.setDensity(0);
	    fog.setQuality(Quality.PerPixel);
	    fog.setStart(Integer.MAX_VALUE);
	    fog.setEnd(Integer.MAX_VALUE);
	    this.setRenderState(fog);
	    // Create non-cullable culling state.
	    final CullState cull = renderer.createCullState();
	    cull.setCullFace(Face.None);
	    this.setRenderState(cull);
	    // Update render state.
	    this.updateRenderState();
	}
	
	@Override
	public void updateGeometricState(float interpolation, boolean initiator) {
		this.getLocalTranslation().set(this.camera.getLocation());
		for(IComponent component : this.components.values()) component.update(interpolation);
		super.updateGeometricState(interpolation, initiator);
	}

	@Override
	public boolean attachComponent(IComponent component) {
		if(component == null || this.components.containsKey(component.getKey().getValue())) return false;
		component.initialize(this, this);
		this.components.put(component.getKey().getValue(), component);
		return true;
	}

	@Override
	public boolean detachComponent(IComponent component) {
		if(component == null) return false;
		component.cleanup();
		return (this.components.remove(component.getKey().getValue()) != null);
	}

	@Override
	public boolean detachComponent(IComponentKey key) {
		if(key == null) return false;
		final IComponent component = this.components.remove(key);
		component.cleanup();
		return (component != null);
	}
	
	/**
	 * Clamp the given 24-hour system hour value.
	 * @param hour The <code>Integer</code> hour value.
	 * @return The <code>Float</code> clamped value.
	 */
	private float clampHour(int hour) {
		if(hour > 24) return 24.0f;
		else if(hour < 0) return 0.0f;
		else return (float)hour;
	}
	
	/**
	 * Convert the given 60-minute system value to 100-minute system value.
	 * @param min The <code>Integer</code> 60-minute value.
	 * @return The <code>Float</code> 100-minute system value.
	 */
	private float convertMinute(int min) {
		if(min > 60) return 1;
		else if(min < 0) return 0;
		else return ((float)min/60.0f)*100.0f;
	}

	@Override
	public void setDayEnd(int hour, int min) {
		this.dayEnd = this.clampHour(hour) + this.convertMinute(min);
		this.diffTransDay = this.dayTransition - this.dayEnd;
	}

	@Override
	public void setDayStart(int hour, int min) {
		this.dayStart = this.clampHour(hour) + this.convertMinute(min);
		this.diffDayTrans = this.dayStart - this.nightTransition;
	}

	@Override
	public void setNightEnd(int hour, int min) {
		this.nightEnd = this.clampHour(hour) + this.convertMinute(min);
		this.diffTransNight = this.nightTransition - this.nightEnd;
	}

	@Override
	public void setNightStart(int hour, int min) {
		this.nightStart = this.clampHour(hour) + this.convertMinute(min);
		this.diffNightTrans = this.nightStart - this.dayTransition;
	}

	@Override
	public void setDayTransition(int hour, int min) {
		this.dayTransition = this.clampHour(hour) + this.convertMinute(min);
		this.diffTransDay = this.dayTransition - this.dayEnd;
		this.diffNightTrans = this.nightStart - this.dayTransition;
	}

	@Override
	public void setNightTransition(int hour, int min) {
		this.nightTransition = this.clampHour(hour) + this.convertMinute(min);
		this.diffTransNight = this.nightTransition - this.nightEnd;
		this.diffDayTrans = this.dayStart - this.nightTransition;
	}

	@Override
	public float getDayEnd() {
		return this.dayEnd;
	}

	@Override
	public float getDayStart() {
		return this.dayStart;
	}

	@Override
	public float getNightEnd() {
		return this.nightEnd;
	}

	@Override
	public float getNightStart() {
		return this.nightStart;
	}

	@Override
	public float getDayTransition() {
		return this.dayTransition;
	}

	@Override
	public float getNightTransition() {
		return this.nightTransition;
	}

	@Override
	public float getDiffTransDay() {
		return this.diffTransDay;
	}

	@Override
	public float getDiffNightTrans() {
		return this.diffNightTrans;
	}

	@Override
	public float getDiffTransNight() {
		return this.diffTransNight;
	}

	@Override
	public float getDiffDayTrans() {
		return this.diffDayTrans;
	}

	@Override
	public IComponent getComponent(IComponentKey key) {
		if(key == null) return null;
		return this.components.get(key);
	}

	@Override
	public void cleanup() {
		for(IComponent component : this.components.values()) component.cleanup();
		this.components.clear();
	}
}
